#include "os_util.hh"
#include "string_util.hh"

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN // avoid including unnecessary WIN32 header files
#include <windows.h>
#include "shlwapi.h"
#pragma comment(lib, "shlwapi.lib")
#include <WinSock2.h>
#define DELIMETER "\\"
#else
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <dirent.h>  
#include <sys/stat.h>
#define DELIMETER "/"
#endif // WIN32

#include <cstdio>
#include <cstring>
#include <climits>
#include <random>

namespace kratos { namespace corelib {

std::string OS::getHostIP(const std::string & host) {
    if (host == "localhost") {
        return "127.0.0.1";
    }
    bool isIp = true;
    for (size_t i = 0; i < host.size(); i++) {
        if ((host.at(i) != '.') && !isdigit(host.at(i))) {
            isIp = false;
            break;
        }
    }
    if (isIp) {
        return host;
    }
    auto ent = gethostbyname(host.c_str());
    if (!ent) {
        return host;
    }
    auto ip = inet_ntoa(*((struct in_addr*)ent->h_addr));
    if (!ip) {
        return "";
    }
    return ip;
}

std::string OS::getExePath() {
    const static int SIZE = 512;
    char path[SIZE] = { 0 };
#ifdef WIN32    
    ::GetModuleFileName(NULL, path, sizeof(path));
#else
    int result = readlink("/proc/self/exe", path, sizeof(path));
    if (result < 0 || (result >= SIZE - 1)) {
        return "";
    }
    path[result] = '\0';
    for (int i = result; i >= 0; i--) {
        if (path[i] == '/') {
            path[i] = '\0';
            break;
        }
    }
    return path;
#endif // WIN32
    std::string temp(path);
    size_t index = temp.rfind("\\");
    std::string execPath;
    execPath.append(temp, 0, index);
    return execPath;
}

std::string OS::getExeName() {
    const static int SIZE = 512;
    char path[SIZE] = { 0 };
#ifdef WIN32    
    ::GetModuleFileName(NULL, path, sizeof(path));
#else
    int result = readlink("/proc/self/exe", path, sizeof(path));
    if (result < 0 || (result >= SIZE - 1)) {
        return "";
    }
    path[result] = '\0';
#endif // WIN32
    return path;
}

bool OS::getFiles(const std::string & directory, const std::string suffix,
    std::vector<std::string>& fileNames) {
    if (!isExists(directory)) {
        return false;
    }
#   ifdef WIN32
    WIN32_FIND_DATAA findData;
    HANDLE handle = INVALID_HANDLE_VALUE;
    std::string path = directory + "\\*.*";
    handle = ::FindFirstFileA(path.c_str(), &findData);
    if (handle == INVALID_HANDLE_VALUE) {
        return false;
    }
    for (;;) {
        if (strcmp(findData.cFileName, ".") == 0 ||
            strcmp(findData.cFileName, "..") == 0) {
            if (!::FindNextFileA(handle, &findData)) {
                break;
            }
            continue;
        }
        std::string fullName = directory + "\\" + findData.cFileName;
        if (suffix.empty()) {
            fileNames.push_back(fullName);
        } else {
            if (StringUtil::endWith(fullName, suffix)) {
                fileNames.push_back(fullName);
            }
        }
        if (!::FindNextFileA(handle, &findData)) {
            break;
        }
    }
    ::FindClose(handle);
#   else
    DIR           *dir = 0;
    struct dirent *ent = 0;
    dir = opendir(directory.c_str());
    if (!dir) {
        return false;
    }
    while (0 != (ent = readdir(dir))) {
        if (!(ent->d_type & DT_DIR)) {
            if (strcmp(ent->d_name, ".") == 0 ||
                strcmp(ent->d_name, "..") == 0) {
                continue;
            }
            std::string fullName = directory + "/" + ent->d_name;
            if (suffix.empty()) {
                fileNames.push_back(fullName);
            } else {
                if (StringUtil::endWith(fullName, suffix)) {
                    fileNames.push_back(fullName);
                }
            }
        }
    }
#   endif // WIN32
    return true;
}

bool OS::isExists(const std::string & path) {
#ifdef WIN32
    return (TRUE == ::PathFileExistsA(path.c_str()));
#else
    return !access(path.c_str(), F_OK);
#endif // WIN32
}

bool OS::removeFile(const std::string & filePath) {
    return (remove(filePath.c_str()) == 0);
}

std::string OS::completePath(const std::string & base, const std::string & sub) {
    if (StringUtil::endWith(base, DELIMETER)) {
        return base + sub;
    } else {
        return base + DELIMETER + sub;
    }
}

bool OS::renameFile(const std::string & srcFileName, const std::string & newFileName) {
    return (rename(srcFileName.c_str(), newFileName.c_str()) == 0);
}

std::string OS::getExtension(const std::string & fileName) {
    auto pos = fileName.find_last_of('.');
    return std::string(fileName.begin() + pos + 1, fileName.end());
}

std::uint32_t OS::getRandom(std::uint32_t a, std::uint32_t b) {
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_int_distribution<std::uint32_t> dist(a, b + 1);
    return dist(mt);
}

}}
